﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
namespace ProceduralGolf
{
    class TextureBlurrer : TextureEffect
    {
        private GraphicsDevice graphics;
        private Effect blurEffect;

        private const int blurFactor = 1;
        private Vector2[] sampleOffsetsH = new Vector2[15];
        private float[] sampleWeightsH = new float[15];
        private Vector2[] sampleOffsetsV = new Vector2[15];
        private float[] sampleWeightsV = new float[15];


        public TextureBlurrer(Game game)
        {
            this.graphics = game.GraphicsDevice;
            blurEffect = game.Content.Load<Effect>("blur");
            spriteBatch = new SpriteBatch(graphics);
        }


        private Texture2D blurHorizontal(Texture2D tex)
        {
            GraphicsMutex.GetMutex().WaitOne();

            Texture2D result;
            RenderTarget2D target = new RenderTarget2D(graphics, tex.Width / blurFactor, tex.Height / blurFactor, 1, graphics.PresentationParameters.BackBufferFormat);
            DepthStencilBuffer stencilBuffer = new DepthStencilBuffer(graphics, tex.Width / blurFactor, tex.Height / blurFactor, graphics.DepthStencilBuffer.Format);
            DepthStencilBuffer oldBuffer = graphics.DepthStencilBuffer;

            blurEffect.Parameters["sampleWeights"].SetValue(sampleWeightsH);
            blurEffect.Parameters["sampleOffsets"].SetValue(sampleOffsetsH);

            graphics.DepthStencilBuffer = stencilBuffer;
            graphics.SetRenderTarget(0, target);
            graphics.Clear(Color.Black);

            blurEffect.Begin();

            foreach (EffectPass pass in blurEffect.CurrentTechnique.Passes)
            {
                spriteBatch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.None);
                pass.Begin();

                spriteBatch.Draw(tex, new Rectangle(0, 0, tex.Width / blurFactor, tex.Height / blurFactor), Color.White);
                pass.End();
                spriteBatch.End();
            }

            blurEffect.End();

            graphics.SetRenderTarget(0, null);
            graphics.DepthStencilBuffer = oldBuffer;

            //oldBuffer.Dispose();
            result = target.GetTexture();
            target.Dispose();
            stencilBuffer.Dispose();

            GraphicsMutex.GetMutex().ReleaseMutex();

            return result;

        }

        private Texture2D blurVertical(Texture2D tex)
        {
            GraphicsMutex.GetMutex().WaitOne();

            Texture2D result;
            RenderTarget2D target = new RenderTarget2D(graphics, tex.Width / blurFactor, tex.Height / blurFactor, 1, graphics.PresentationParameters.BackBufferFormat);
            DepthStencilBuffer stencilBuffer = new DepthStencilBuffer(graphics, tex.Width / blurFactor, tex.Height / blurFactor, graphics.DepthStencilBuffer.Format);
            DepthStencilBuffer oldBuffer = graphics.DepthStencilBuffer;

            blurEffect.Parameters["sampleWeights"].SetValue(sampleWeightsV);
            blurEffect.Parameters["sampleOffsets"].SetValue(sampleOffsetsV);

            graphics.DepthStencilBuffer = stencilBuffer;
            graphics.SetRenderTarget(0, target);
            graphics.Clear(Color.TransparentBlack);

            blurEffect.Begin();

            foreach (EffectPass pass in blurEffect.CurrentTechnique.Passes)
            {
                spriteBatch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.None);
                pass.Begin();

                spriteBatch.Draw(tex, new Rectangle(0, 0, tex.Width / blurFactor, tex.Height / blurFactor), Color.White);
                pass.End();
                spriteBatch.End();
            }

            blurEffect.End();

            graphics.SetRenderTarget(0, null);
            graphics.DepthStencilBuffer = oldBuffer;

            //oldBuffer.Dispose();
            result = target.GetTexture();
            stencilBuffer.Dispose();
            target.Dispose();

            GraphicsMutex.GetMutex().ReleaseMutex();
            return result;
        }

        //private Texture2D blurHorizontal(Texture2D tex)
        //{
        //    Texture2D result;
        //    RenderTarget2D target = new RenderTarget2D(graphics, tex.Width / blurFactor, tex.Height / blurFactor, 1, graphics.PresentationParameters.BackBufferFormat);
            
        //    blurEffect.Parameters["sampleWeights"].SetValue(sampleWeightsH);
        //    blurEffect.Parameters["sampleOffsets"].SetValue(sampleOffsetsH);
            
        //    graphics.SetRenderTarget(0, target);

        //    blurEffect.Begin();

        //    foreach (EffectPass pass in blurEffect.CurrentTechnique.Passes)
        //    {
        //        spriteBatch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.None);
        //        pass.Begin();

        //        spriteBatch.Draw(tex, new Rectangle(0, 0, tex.Width / blurFactor, tex.Height / blurFactor), Color.White);
        //        pass.End();
        //        spriteBatch.End();
        //    }

        //    blurEffect.End();

        //    graphics.SetRenderTarget(0, null);
     
        //    result = target.GetTexture();
        //    target.Dispose();
        //    return result;

        //}

        //private Texture2D blurVertical(Texture2D tex)
        //{
        //    Texture2D result;
        //    RenderTarget2D target = new RenderTarget2D(graphics, tex.Width / blurFactor, tex.Height / blurFactor, 1, graphics.PresentationParameters.BackBufferFormat);

        //    blurEffect.Parameters["sampleWeights"].SetValue(sampleWeightsV);
        //    blurEffect.Parameters["sampleOffsets"].SetValue(sampleOffsetsV);

        //    graphics.SetRenderTarget(0, target);

        //    blurEffect.Begin();

        //    foreach (EffectPass pass in blurEffect.CurrentTechnique.Passes)
        //    {
        //        spriteBatch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.None);
        //        pass.Begin();

        //        spriteBatch.Draw(tex, new Rectangle(0, 0, tex.Width / blurFactor, tex.Height / blurFactor), Color.White);
        //        pass.End();
        //        spriteBatch.End();
        //    }

        //    blurEffect.End();

        //    graphics.SetRenderTarget(0, null);
        //    result = target.GetTexture();
        //    target.Dispose();

        //    return result;
        //}


        public Texture2D blur(Texture2D tex)
        {
            return blur(tex, 1);
        }

        public Texture2D blur(Texture2D tex, float scale)
        {
            Setup(tex.Width, tex.Height, scale);
            Texture2D texBlurH = blurHorizontal(tex);
            Texture2D texBlurHV = blurVertical(texBlurH);
            return texBlurHV;
        }

        public Texture2D blur(Texture2D tex, int scale)
        {
            Texture2D blurredTex = tex;
            for (int i = scale; i >= 1; --i)
            {
                Setup(tex.Width, tex.Height, i);
                blurredTex = blurHorizontal(blurredTex);
                blurredTex = blurVertical(blurredTex);
                
            }
            return blurredTex;
        }

        private void Setup(int Width, int Height, float scale)
        {
            Vector2 texelSize = new Vector2(1f / Width, 1f / Height);

            SetBlurParameters(texelSize.X, 0, ref sampleOffsetsH, ref sampleWeightsH, scale);
            SetBlurParameters(0, texelSize.Y, ref sampleOffsetsV, ref sampleWeightsV, scale);
        }

        private void SetBlurParameters(float dx, float dy, ref Vector2[] vSampleOffsets, ref float[] fSampleWeights)
        {
            SetBlurParameters(dx, dy, ref vSampleOffsets, ref fSampleWeights, 1);
        }

        // Borrowed from "Shadow Mapping" by Andrew Joli
        // http://www.ziggyware.com/readarticle.php?article_id=161
        private void SetBlurParameters(float dx, float dy, ref Vector2[] vSampleOffsets, ref float[] fSampleWeights, float scale)
        {
            // The first sample always has a zero offset.
            fSampleWeights[0] = ComputeGaussian(0, 1);
            vSampleOffsets[0] = new Vector2(0);

            // Maintain a sum of all the weighting values.
            float totalWeights = fSampleWeights[0];

            // Add pairs of additional sample taps, positioned
            // along a line in both directions from the center.
            for (int i = 0; i < 15 / 2; i++)
            {
                
                // Store weights for the positive and negative taps.
                float weight = ComputeGaussian(i + 1, scale);

                fSampleWeights[i * 2 + 1] = weight;
                fSampleWeights[i * 2 + 2] = weight;

                totalWeights += weight * 2;

                // The 1.5 offset kicks things off by
                // positioning us nicely in between two texels.
                float sampleOffset = i * scale * 2 + 1.5f;

                Vector2 delta = new Vector2(dx, dy) * sampleOffset;

                // Store texture coordinate offsets for the positive and negative taps.
                vSampleOffsets[i * 2 + 1] = delta;
                vSampleOffsets[i * 2 + 2] = -delta;
            }

            // Normalize the list of sample weightings, so they will always sum to one.
            for (int i = 0; i < fSampleWeights.Length; i++)
                fSampleWeights[i] /= totalWeights;
        }

        // Borrowed from "Shadow Mapping" by Andrew Joli
        // http://www.ziggyware.com/readarticle.php?article_id=161
        private float ComputeGaussian(float n, float scale)
        {
            float theta = 2.0f + float.Epsilon * scale;
            return theta = (float)((1.0 / Math.Sqrt(2 * Math.PI * theta)) * Math.Exp(-(n * n) / (2 * theta * theta)));
        }

    }
}
